package com.rw.database;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rw.rule.LoadRule;
import com.rw.rule.ReadWriteRule;
import com.rw.rule.load.SimpleDataSourceRule;
import com.rw.rule.readWrite.DefaultReadWriteRule;

/**
 * 读写分离数据源
 * 
 * @author lkclkc88(lkclkc88@sina.com)
 * @date : 2015年11月23日
 */
public class ReadWriteDataSource extends DataSourceAdapter {

	private static Logger log = LoggerFactory
			.getLogger(ReadWriteDataSource.class);

	private DataSource writer;

	private List<DataSource> readers;
	/**
	 * 数据源负载规则
	 */
	private LoadRule loadRule;
	/**
	 * 读写规则
	 */
	private List<ReadWriteRule> readWriteRules;
	/**
	 * slaves节点数量
	 */
	private int readerSize;

	/**
	 * 全局可写，默认为true
	 */
	private boolean gloabCanWrite = true;

	private Connection getSlaveConnection() throws SQLException {
		if (readerSize == 0) {
			return dataSource.getConnection();
		} else if (readerSize == 1) {
			return readers.get(0).getConnection();
		} else {
			return loadRule.process(readers).getConnection();
		}

	}

	private Connection getSlaveConnection(String username, String password)
			throws SQLException {
		if (readerSize == 0) {
			return dataSource.getConnection(username, password);
		} else if (readerSize == 1) {
			return readers.get(0).getConnection(username, password);
		} else {
			return loadRule.process(readers).getConnection(username, password);
		}
	}

	/**
	 * 是否执行写操作
	 * 
	 * @return
	 */
	private boolean isCanWrite() {
		boolean result = false;
		/**
		 * 如果全局设定不能写数据，直接返回false;
		 */
		if (this.gloabCanWrite) {
			/**
			 * 发现一个规则能写，直接返回true，只有全部规则都为读时，才设定为读
			 */
			for (ReadWriteRule rule : readWriteRules) {
				if (!rule.isRead(this)) {
					result = true;
					break;
				}
			}
			;
		}
		if (log.isDebugEnabled()) {
			log.debug("isCanWrite : {}", result);
		}
		return result;
	}

	public Connection getConnection() throws SQLException {
		if (isCanWrite()) {
			return dataSource.getConnection();
		} else {
			return getSlaveConnection();
		}

	}

	public Connection getConnection(String username, String password)
			throws SQLException {
		if (isCanWrite()) {
			return dataSource.getConnection(username, password);
		} else {
			return getSlaveConnection(username, password);
		}
	}

	private <T> void cleanNull(Collection<T> c) {
		if (null == c) {
			return;
		}
		Iterator<T> it = c.iterator();
		while (it.hasNext()) {
			if (it.next() == null) {
				it.remove();
			}
		}
	}

	/**
	 * 初始化线程池，如果线程池能写，则master不能为空。
	 * 
	 * @throws SQLException
	 */
	public void init() throws SQLException {
		cleanNull(readers);
		if (null != readers) {
			readerSize = readers.size();
		}

		if (null == loadRule) {
			log.warn("loadRule is null ,use default rule  SimpleDataSourceRule");
			loadRule = new SimpleDataSourceRule();
		}
		if (readerSize > 0) {
			loadRule.initRule(readers);
		}

		cleanNull(readWriteRules);
		if (null == readWriteRules || readWriteRules.isEmpty()) {
			readWriteRules = new LinkedList<ReadWriteRule>();
			log.warn("readWriteRules is null ,use default rule DefaultReadWriteRule");
			readWriteRules.add(new DefaultReadWriteRule());
		}

		if (this.gloabCanWrite) {

			if (null == writer) {
				throw new SQLException("master can't be null");
			}
			this.dataSource = writer;
		} else {
			if (null == writer) {
				if (readers == null || readers.isEmpty()) {
					throw new SQLException("must more than one datasource");
				} else {
					this.dataSource = readers.get(0);
				}
			} else {
				this.dataSource = writer;
			}
		}

	}

	public DataSource getWriter() {
		return writer;
	}

	public void setWriter(DataSource writer) {
		this.writer = writer;
	}

	public List<DataSource> getReaders() {
		return readers;
	}

	public void setReaders(List<DataSource> readers) {
		this.readers = readers;
	}

	public LoadRule getLoadRule() {
		return loadRule;
	}

	public void setLoadRule(LoadRule loadRule) {
		this.loadRule = loadRule;
	}

	public List<ReadWriteRule> getReadWriteRules() {
		return readWriteRules;
	}

	public void setReadWriteRules(List<ReadWriteRule> readWriteRules) {
		this.readWriteRules = readWriteRules;
	}

	public boolean isGloabCanWrite() {
		return gloabCanWrite;
	}

	public void setGloabCanWrite(boolean gloabCanWrite) {
		this.gloabCanWrite = gloabCanWrite;
	}

	public int getReaderSize() {
		return readerSize;
	}

}
